/*
 * Copyright 2011 Angel Sanadinov
 *
 * This file is part of VBox WMI.
 *
 * VBox WMI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * VBox WMI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with VBox WMI.  If not, see <http://www.gnu.org/licenses/>.
 */

package Utilities;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

/**
 * A servlet output stream for compressing data using the GZIP algorithm.
 *
 * @see GzipCompressionWrapper
 *
 * @author Angel Sanadinov
 */
public class GzipHttpServletOutputStream extends ServletOutputStream
{
    private GZIPOutputStream compressedOutputStream;    //compressed output stream
    private ByteArrayOutputStream compressedDataBuffer; //buffer used for the data compression
    private HttpServletResponse rawResponse;            //raw http servlet response
    private boolean isClosed;                           //denotes whether this stream is closed

    /**
     * Constructs the compression stream using the specified data.
     *
     * @param response the raw servlet response
     * @throws IOException if an I/O error occurs
     */
    public GzipHttpServletOutputStream(HttpServletResponse response) throws IOException
    {
        this.rawResponse = response;
        this.compressedDataBuffer = new ByteArrayOutputStream();
        this.compressedOutputStream = new GZIPOutputStream(compressedDataBuffer);
        this.isClosed = false;
    }

    @Override
    public void write(int b) throws IOException
    {
        if(!isClosed)
            compressedOutputStream.write(b);
        else
            throw new IOException("Attempting to write to a closed stream");
    }

    @Override
    public void write(byte[] b) throws IOException
    {
        this.write(b, 0, b.length);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException
    {
        if(!isClosed)
            compressedOutputStream.write(b, off, len);
        else
            throw new IOException("Attempting to write to a closed stream");
    }

    @Override
    public void flush() throws IOException
    {
        if(!isClosed)
            compressedOutputStream.flush();
        else
            throw new IOException("Attempting to flush a closed stream");
    }

    /**
     * Finalises the compression of the data sent through the stream, sends the
     * response headers and commits the response. <br><br>
     *
     * All underlying streams are closed. 
     * After the stream is closed, it can no longer be used for sending data.
     *
     * @throws IOException if an I/O error occurs
     */
    @Override
    public void close() throws IOException
    {
        if(!isClosed)
        {
            //finalises the compression
            compressedOutputStream.finish();
            //retrieves the byte representation of the data in the buffer
            byte[] compressedData = compressedDataBuffer.toByteArray();
            //retrieves the raw output stream for the response
            ServletOutputStream rawOutputStream = rawResponse.getOutputStream();

            //sets the response headers
            rawResponse.setHeader("Content-Length", Integer.toString(compressedData.length));
            rawResponse.setHeader("Content-Encoding", "gzip");
            //writes out the compressed data
            rawOutputStream.write(compressedData);
            rawOutputStream.flush();

            //closes the underlying streams
            compressedOutputStream.close();
            rawOutputStream.close();

            //cleans up
            compressedOutputStream = null;
            compressedDataBuffer = null;
            rawResponse = null;

            isClosed = true;
        }
        else
            throw new IOException("Attempting to close a closed stream");
    }
}
